import { ZipPos } from '@/modules/structureCache'
import { RemoteHarvestData, RemoteSourceInfo } from './types'
import LayoutController from '@/modules/layout'
import { cached, defineGlobalProperty, isInterval, isMyRoom, isOfficialShard, isRoomInterval } from '@/ayaka'
import { ObserveTask } from '../observe/types'
import ObserveTaskManager from '../observe'
import { isCenterNineRoom, isCenterRoom, isHighWayRoom } from '@/utils/common'
import { goTo } from '@/modules/move'
import { getCreeps } from '@/modules/structures/spawn'
import { RoomSafeState } from '@/room/state/types'
import { remoteGuardWork, spawnRemoteGuard } from './remoteGuard'
import { remoteReserverWork, spawnRemoteReserver } from './remoteReserver'
import { remoteCarrierWork, spawnRemoteCarrier } from './remoteCarrier'
import { remoteHarvesterWork, spawnRemoteHarvester } from './remoteHarvester'
import { getFreeSpace } from '@/utils/position'
import { CONTROLLER_ENERGY } from '@/utils/constants'
import { isEnemy } from '@/utils/war'

const refreshRooms = new Set<string>()

/**
 * 外矿任务管理器
 * TODO: 没有考虑中央九房间的情况
 */
export default class RemoteHarvestTaskManager {
    public static run(room: MyRoom) {
        // 私服自动寻找外矿，官服最好根据自己的需要手动开启
        if (!isOfficialShard() && Game.flags[`autoRemoteHarvest_${room.name}`]) {
            RemoteHarvestTaskManager.findRemoteRoom(room)
        }
        RemoteHarvestTaskManager.refresh(room)
        RemoteHarvestTaskManager.execute(room)
    }

    /**
     * 获取外矿 memory
     */
    public static getMemory(room: MyRoom) {
        if (!room.memory.remoteHarvest) {
            room.memory.remoteHarvest = {
                tasks: [],
            }
        }
        return room.memory.remoteHarvest
    }

    /**
     * 执行外矿任务
     */
    public static execute(room: MyRoom) {
        const { tasks } = RemoteHarvestTaskManager.getMemory(room)

        if (room.controller.level < 2) return

        tasks.forEach((task) => {
            const remoteRoom = Game.rooms[task.roomName]
            const data = task.data
            const remoteReservers = getCreeps(room, task.remoteReserver)
            const remoteCarriers = getCreeps(room, task.remoteCarrier)
            const remoteGuards = getCreeps(room, task.remoteGuard)

            if (task.isCenterNine && !task.isCenter && remoteRoom) {
                remoteRoom.enemys = remoteRoom.find(FIND_HOSTILE_CREEPS).filter(isEnemy)
            }

            // 孵化相关
            if (
                room.memory.state.safe?.state === RoomSafeState.normal &&
                (!data.restartTime || data.restartTime <= Game.time) &&
                (!task.isCenterNine ||
                    (room.controller.level >= 7 && room.energyCapacityAvailable >= CONTROLLER_ENERGY[7]))
            ) {
                if (data.isDanger) {
                    // 危险状态孵化外矿守卫
                    spawnRemoteGuard(room, task, remoteRoom?.enemys)
                } else {
                    if (remoteRoom && !task.isCenterNine) {
                        if ((remoteRoom.controller?.reservation?.ticksToEnd || 0) < 2500) {
                            spawnRemoteReserver(room, task)
                        }

                        if (isRoomInterval(remoteRoom, 567) && remoteRoom.controller && !task.data.controllerFreePos) {
                            task.data.controllerFreePos = getFreeSpace(remoteRoom.controller.pos).length
                        }

                        if (remoteRoom.controller && !task.data.controllerFreePos) {
                            // 控制器被堵了也孵化守卫
                            spawnRemoteGuard(room, task)
                        } else {
                            if (isRoomInterval(room, 211)) {
                                const invaderCore = remoteRoom.find(FIND_HOSTILE_STRUCTURES, {
                                    filter: { structureType: STRUCTURE_INVADER_CORE },
                                })[0]
                                if (invaderCore) {
                                    spawnRemoteGuard(room, task)
                                }
                            }
                        }
                    }

                    // 九区但不是中央房间，孵化守卫
                    if (task.isCenterNine && !task.isCenter) {
                        spawnRemoteGuard(room, task, remoteRoom?.enemys)
                    }
                    spawnRemoteCarrier(room, task, remoteCarriers)
                    spawnRemoteHarvester(room, task)
                }
            }

            remoteGuardWork(remoteGuards, task, remoteRoom)
            remoteReserverWork(remoteReservers, task, remoteRoom)
            remoteHarvesterWork(room, task, remoteRoom)
            remoteCarrierWork(room, remoteCarriers, task, remoteRoom)
        })
    }

    /**
     * 判断房间是否值得作为外矿
     */
    public static isWorthy(room: MyRoom, targetRoom: Room) {
        if (targetRoom.controller && (targetRoom.controller.owner || targetRoom.controller.reservation)) return false

        if (Game.map.getRoomLinearDistance(room.name, targetRoom.name) >= 3) return false

        return true
    }

    /**
     * 添加外矿任务
     */
    public static addTask(room: MyRoom, targetRoom: Room, sourceId?: Id<Source>) {
        if (!room.memory.center) return false

        const memory = RemoteHarvestTaskManager.getMemory(room)
        const task = RemoteHarvestTaskManager.searchTaskByName(room, targetRoom.name)
        // 其他房间已经有这个外矿任务了
        if (!task && RemoteHarvestTaskManager.hasTask(targetRoom.name)) return false
        // sourceId 是一种补充手段，task 存在则必须指定 sourceId，否则重复
        if (task && !sourceId) return false

        const isCenterNine = isCenterNineRoom(targetRoom.name)
        const isCenter = isCenterRoom(targetRoom.name)
        // 七级及以上才能开九区外矿
        if (isCenterNine && room.controller.level < 7) return false

        const targets = [...targetRoom.find(FIND_SOURCES), ...(isCenterNine ? targetRoom.find(FIND_MINERALS) : [])]
        const sources = targets.filter((source) => !sourceId || source.id === sourceId)
        if (!sources.length) return false

        const centerPos = new RoomPosition(room.memory.center[0], room.memory.center[1], room.name)
        const roads = RemoteHarvestTaskManager.getRoads(room)

        const sourceInfo = sources.reduce((result, source, index) => {
            const roadsPos = LayoutController.findPath(centerPos, source.pos, { roads })
            if (!roadsPos || !roadsPos.length) return result

            // 最后一个位置必须是 source 旁边，否则说明寻路错误
            if (!roadsPos[roadsPos.length - 1].isNearTo(source.pos)) return result
            const roadMap = zipPosArr(roadsPos)

            result[source.id] = {
                x: source.pos.x,
                y: source.pos.y,
                remoteHarvester: [],
                roads: roadMap,
                roadLength: roadsPos.length,
                type: source instanceof Source ? 'energy' : source.mineralType,
                timeToRegeneration: Game.time + (source.ticksToRegeneration || -1),
            }

            // 不是最后一个 source 就要前面 source 得到的道路信息加上
            if (index < sources.length - 1) {
                for (const roomName in roadMap) {
                    if (!roads[roomName]) roads[roomName] = []
                    // 去重加入
                    roads[roomName] = [...new Set([...roads[roomName], ...roadMap[roomName]])]
                }
            }

            return result
        }, {} as Record<Id<Source | Mineral>, RemoteSourceInfo>)

        if (!Object.keys(sourceInfo).length) return false

        const data: RemoteHarvestData = {
            source: task ? { ...task.data.source, ...sourceInfo } : sourceInfo,
            controllerFreePos: targetRoom.controller ? getFreeSpace(targetRoom.controller.pos).length : 0,
        }

        if (task) {
            task.data = data
        } else {
            memory.tasks.push({
                roomName: targetRoom.name,
                remoteCarrier: [],
                remoteReserver: [],
                remoteGuard: [],
                isCenterNine,
                isCenter,
                data,
            })
        }

        return true
    }

    /**
     * 搜索指定房间的外矿任务
     */
    public static searchTaskByName(room: MyRoom, roomName: string) {
        const memory = RemoteHarvestTaskManager.getMemory(room)
        return memory.tasks.find((task) => task.roomName === roomName)
    }

    /**
     * 移除指定房间的外矿任务
     */
    public static removeTask(room: MyRoom, roomName: string) {
        const memory = RemoteHarvestTaskManager.getMemory(room)
        const index = memory.tasks.findIndex((task) => task.roomName === roomName)
        if (index >= 0) memory.tasks.splice(index, 1)
    }

    /**
     * 判断是否有某个房间的外矿任务
     */
    public static hasTask(roomName: string) {
        return !!Object.values(Game.rooms).find((room) => {
            if (!isMyRoom(room)) return false
            return !!RemoteHarvestTaskManager.searchTaskByName(room, roomName)
        })
    }

    /**
     * 获取所有外矿道路
     */
    public static getRoads(room: MyRoom) {
        const tasks = RemoteHarvestTaskManager.getMemory(room).tasks
        const roadSet: { [roomName: string]: Set<ZipPos> } = {}
        tasks.forEach(({ data }) => {
            Object.values(data.source).forEach((sourceInfo) => {
                for (const roomName in sourceInfo.roads) {
                    if (!roadSet[roomName]) roadSet[roomName] = new Set()
                    sourceInfo.roads[roomName].forEach((pos) => roadSet[roomName].add(pos))
                }
            })
        })
        const roads: { [roomName: string]: ZipPos[] } = {}
        for (const roomName in roadSet) {
            roads[roomName] = [...roadSet[roomName]]
        }
        return roads
    }

    /**
     * 添加外矿观察任务
     */
    public static addObserveTask(room: MyRoom, roomName: string, force = false, timeout = 1000) {
        ObserveTaskManager.addTask(room, {
            name: `remoteHarvest-${roomName}`,
            type: 'remoteHarvest',
            roomNames: [roomName],
            roomIndex: 0,
            scouts: [],
            data: {
                force,
            },
            timeout,
        })
    }

    /**
     * 获取默认可能的外矿房间
     */
    public static getDefaultRemoteRoom(room: MyRoom) {
        // 根据房间出口往外扩展两次
        const rooms = new Set<string>()
        const exits = Game.map.describeExits(room.name)

        if (!exits) return []

        // 判断某房间可能作为外矿吗（需要实地考虑才能真正确认）
        const judge = cached((roomName: string) => {
            const judgeRoom = Game.rooms[roomName]

            if (isHighWayRoom(roomName) || isCenterNineRoom(roomName)) return false
            if (Game.map.getRoomStatus(room.name).status !== Game.map.getRoomStatus(roomName).status) return false
            if (judgeRoom && !RemoteHarvestTaskManager.isWorthy(room, judgeRoom)) return false

            return true
        })

        for (const roomName of Object.values(exits)) {
            if (!judge(roomName)) {
                continue
            }
            rooms.add(roomName)
            const exits2 = Game.map.describeExits(roomName)
            if (!exits2) continue
            for (const roomName2 of Object.values(exits2)) {
                if (judge(roomName2)) {
                    rooms.add(roomName2)
                }
            }
        }

        return [...rooms].sort(
            (r1, r2) => Game.map.getRoomLinearDistance(room.name, r1) - Game.map.getRoomLinearDistance(room.name, r2),
        )
    }

    /**
     * 寻找外矿房间
     */
    protected static findRemoteRoom(room: MyRoom, force?: boolean) {
        if (room.controller.level <= 3 && !force) return
        if ((room.controller.level === 8 || !isInterval(6300)) && !force) return

        // 不同等级房间允许开的外矿数量
        const number = room.controller.level < 7 ? 3 : room.controller.level === 7 ? 5 : 7
        if (room.memory.remoteHarvest.tasks.length >= number) return

        const rooms = RemoteHarvestTaskManager.getDefaultRemoteRoom(room)
        if (!rooms.length) return

        for (const roomName of rooms) {
            if (ObserveTaskManager.getTaskByName(room, `remoteHarvest-${roomName}`)) continue
            RemoteHarvestTaskManager.addObserveTask(room, roomName)
        }
    }

    /**
     * 刷新任务数据
     */
    public static refresh(room: MyRoom) {
        if (refreshRooms.has(room.name)) return
        refreshRooms.add(room.name)

        const memory = RemoteHarvestTaskManager.getMemory(room)
        memory.tasks.forEach((task) => {
            Object.values(task.data.source).forEach((sourceInfo) => {
                if (sourceInfo.type) return

                // 刷上 type 属性
                sourceInfo.type = 'energy'
            })
        })
    }
}

/**
 * 将位置转换为各房间的压缩坐标
 */
export const zipPosArr = (posArr: RoomPosition[]) => {
    const result: { [roomName: string]: ZipPos[] } = {}
    posArr.forEach((pos) => {
        if (!result[pos.roomName]) result[pos.roomName] = []
        result[pos.roomName].push(`${pos.x}/${pos.y}`)
    })
    return result
}

/**
 * 外矿房间观察
 */
export const remoteObserve = (creep: Creep, task: ObserveTask) => {
    const myRoom = Game.rooms[creep.memory.belong]
    if (!myRoom || !isMyRoom(myRoom)) {
        task.done = true
        return
    }

    if (creep.body.length <= 1 && creep.room.controller && !creep.pos.isNearTo(creep.room.controller)) {
        goTo(creep, creep.room.controller.pos)
    }
    // 不同等级房间允许开的外矿数量
    const { force } = (task.data as { force: boolean }) || {}
    const number = myRoom.controller.level < 7 ? 3 : myRoom.controller.level === 7 ? 5 : 7
    if (
        !RemoteHarvestTaskManager.isWorthy(myRoom, creep.room) ||
        (!force && myRoom.memory.remoteHarvest.tasks.length >= number)
    ) {
        task.done = true
        return
    }

    if (creep.room.name === task.roomNames[task.roomIndex || 0] && Game.cpu.bucket > 100) {
        RemoteHarvestTaskManager.addTask(myRoom, creep.room)
        task.done = true
        return
    }
}

defineGlobalProperty('RemoteHarvest', {
    value: RemoteHarvestTaskManager,
})
